package ngramencoding;

import java.io.*;
import java.util.*; // for List

/** ============================================================================
 * A classe Codificar codifica as seqüências de nucleotídeos dos genes ribossomais 16S 
 * baseando-se na freqüência de cada trigram, ou seja, essa classe normaliza a 
 * freqüência de cada brigram entre [0,1]; o trigram de maior 
 * freqüência fica com 1 e os trigrams restantes ficam em razão deste.
 * @author Susan Higashi
 */ 
public class Encoder 
{
    /**
     * Lista de nucleotídeos.
     */
    private String nucletoides[] = {"adenine", "thymine", "cytosine", "guanine"};
    /**
     * Armazena a freqüência para todas os possíveis (m^n = 4^3 = 64) trigrams.
     */
    private float freqtrigram[][];
    /**
     * Arquivo de leitura que contém as freqüências dos trigrams.
     */
    private BufferedReader readingFile;
    /**
     * Arquivo de escrita que contém a matriz de entrada para a rede neural.
     */
    private BufferedWriter writingFile;
    /**
     * Arquivos de entrada (*.frq) para processamento.
     */
    private File files[];
    /**
     * Diretório onde será gravado o arquivo que contém a matriz de entrada 
     * para a rede a rede neural.
     */
    private File outputDir;
    

    /** ------------------------------------------------------------------------
     * Seleciona os arquivos de freqüência dos trigrams e o diretório de destino 
     * do arquivo de saída e processa os arquivos de freqüência.
     * 
     * @param outputFile Nome do arquivo de saída que será matriz de entrada da rede.
     * @return tempo inicial de codificação.
     */
    public long createEntries(String outputFile) throws IOException
    {
        long initialTime = System.currentTimeMillis();  // pega o tempo corrente
        createFreqTrigram();                        // inicializa a matriz de frequencias com zeros
        Utilities utilOs = new Utilities();         // pega o sistema operacional 
        String os = utilOs.getOS();
        
        // diretorio de saida do arquivo de entrada das RN
        String outputDirPath = "/mnt/files/artigo/dev/trunk/ngramencoding/data/4-txt/";
        setOutputDir(outputDirPath);
        
        try  //cria arquivo de escrita
        {
            if (os.equalsIgnoreCase("windows"))
                    setWritingFile(new BufferedWriter(new FileWriter(getOutputDir().getPath()+"\\"+outputFile)));
            else
                    setWritingFile(new BufferedWriter(new FileWriter(getOutputDir().getPath()+"/"+outputFile)));
        } 
        catch (IOException e1) { e1.printStackTrace(); }
        
        // CODIFICA A PARTE POSITIVA 
        File startingDir = new File("/mnt/files/artigo/dev/trunk/ngramencoding/data/3-frq/bradyrhizobium-57/");
        Utilities utilFl = new Utilities(); 
        List <File> files = utilFl.getFileListing(startingDir);
        
        // processa todos os arquivos com saida positiva para a rede Rhizobium
        for (File file : files) 
        {
            setReadingFile(new BufferedReader(new FileReader(file)));
            String textoArq = readFile(); // recebe o conteudo do arq .frq
            codificarSeqLin(textoArq);
            
            recordFile(1); 
            getReadingFile().close();
        }
        
        // CODIFICA A PARTE NEGATIVA 
        File startingDirNeg = new File("/mnt/files/artigo/dev/trunk/ngramencoding/data/3-frq/bradyrhizobium-57/");
        Utilities utilFlNeg = new Utilities(); 
        List <File> filesNeg = utilFlNeg.getFileListing(startingDirNeg);
        
        // processa todos os arquivos com saida negativa para a rede Rhizobium
        for (File file : filesNeg)
        {
            setReadingFile(new BufferedReader(new FileReader(file)));
            String textoArq = readFile();
            codificarSeqLin(textoArq);
            recordFile(0);  
            getReadingFile().close();
        }        

        getWritingFile().close();
        
        return initialTime;
    }
    
    /** ------------------------------------------------------------------------
     * Codifica um gene (16S) através de sua seqüência linear de nucleotídeos.
     * Utiliza-se a freqüência normalizada de cada um dos possíveis trigrams 
     * de nucleotídeos, perfazendo um total de 64 possíveis combinações. 
     * 
     * @param textoArq contém o arquivo de freqüência trigram para uma seqüência 
     * de nucleotídeos.
     */
    private void codificarSeqLin(String textoArq)
    {
        int codNucleot1, codNucleot2, codNucleot3;
        float frequencia;
		
        StringTokenizer tokens = new StringTokenizer(textoArq, " <>\n\t\r"); // \r: posiciona o cursor no início da linha atual
        tokens.nextToken();
        		
        String nucleot1 = tokens.nextToken();
        String nucleot2 = tokens.nextToken();
        String nucleot3 = tokens.nextToken();
        		
        int maiorFreq = Integer.parseInt(tokens.nextToken()); // seta o trigram de maior frequencia
        setFreqTrigram(getNucletoideCode(nucleot1)+getNucletoideCode(nucleot2), getNucletoideCode(nucleot3), 1);
        
        tokens.nextToken(); tokens.nextToken(); tokens.nextToken();
        tokens.nextToken(); tokens.nextToken(); tokens.nextToken();
        
        while (tokens.hasMoreTokens())
        {
            codNucleot1 = getNucletoideCode(tokens.nextToken()); //1o. nucleotideo
            codNucleot2 = getNucletoideCode(tokens.nextToken()); //2o. nucleotideo
            codNucleot3 = getNucletoideCode(tokens.nextToken()); //3o. nucleotideo
            frequencia = Float.parseFloat(tokens.nextToken()); // frequencia do bibram: codNucleot1 codNucleot2
            setFreqTrigram(codNucleot1, codNucleot2, frequencia / maiorFreq);
            tokens.nextToken(); tokens.nextToken(); tokens.nextToken();
            tokens.nextToken(); tokens.nextToken(); tokens.nextToken();
        }
    }
    
    /** ------------------------------------------------------------------------
     * 
     */
    void setOutputDir(String outputDirPath)
    {
        this.outputDir = new File(outputDirPath);  
    }

    /** ------------------------------------------------------------------------
     * Retorna a freqüência referente ao trigram da posição i,j.
     * @param i código do primeiro nucleotídeo
     * @param j código do segundo nucleotídeo.
     * @return retorna a freqüência referente ao trigram da posição i,j.
     */
    public float getFreqTrigram(int i, int j) 
    { 
        return this.freqtrigram[i][j]; 
    }
    
    /** ------------------------------------------------------------------------
     * Altera o valor do atributo freqtrigram para o parâmetro 'value' na posição 
     * (i,j).
     * @param i posição do primeiro nucleotídeo.
     * @param j posição do segundo nucleotídeo. 
     * @param newFreqTrigram freqüência do trigram referente a posição (i,j).
     */
    public void setFreqTrigram(int i, int j, float newFreqTrigram) 
    { 
        freqtrigram[i][j] = newFreqTrigram; 
    }
    
    /** ------------------------------------------------------------------------
     * Retorna o array de arquivos de freqüência para processamento. 
     */
    public File[] getFiles() 
    { 
        return this.files; 
    }
    
    /** ------------------------------------------------------------------------
     * Retorna o arquivo de freqüência i. 
     */
    public File getFile(int i) 
    { 
        return this.files[i]; 
    }
    
    /** ------------------------------------------------------------------------
     * Retorna o diretório onde será gravado o arquivo que contém a matriz de 
     * entrada para a rede neural.
     * @return diretório de saída. 
     */
    public File getOutputDir() 
    { 
        return this.outputDir; 
    }
    
    /** ------------------------------------------------------------------------
     * Altera o valor do atributo writingFile para o parâmetro 'newWritingFile'.
     * @param newWritingFile arquivo da matriz de entrada para rede neural.
     */
    public void setWritingFile(BufferedWriter newWritingFile) 
    { 
        this.writingFile = newWritingFile; 
    }
    
    /** ------------------------------------------------------------------------
     * Retorna o arquivo de freqüência para leitura. 
     * @return arquivo de leitura 'readingFile'.
     */
    public BufferedReader getReadingFile() 
    {
        return this.readingFile;
    }
    
    /** ------------------------------------------------------------------------
     * Retorna o arquivo de escrita da matriz de entrada da rede.
     * @return arquivo de escrita 'writingFile'
     */
    public BufferedWriter getWritingFile()
    {
        return this.writingFile;
    }
    
    /** ------------------------------------------------------------------------
     * Altera o valor do atributo 'readingFile' para o parâmetro newReadingFile.
     * @param newReadingFile arquivo das freqüências para leitura.
     */
    public void setReadingFile(BufferedReader newReadingFile)
    {
        this.readingFile = newReadingFile;
    }
    
    /** ------------------------------------------------------------------------
     * Inicializa o vetor freqtrigram para as 16 possibilidades de trigrams de 
     * nucleotídeos.
     */
    public void createFreqTrigram()
    {
        this.freqtrigram = new float[16][4];
        
        for(int i = 0; i < 16; i++)
        {
            for(int j = 0; j < 4; j++)
            {
                this.freqtrigram[i][j] = 0;
            }
        }
    }
    
    /** ------------------------------------------------------------------------
     * Retorna o código do nucleotídeo que está sendo recebido pelo parâmetro 
     * nucleotideo.
     * @param nucleotide nome do nucleotídeo (adenine, thymine, cytosine, guanine). 
     * @return retona o código referente ao nucleotídeo nucleotide.
     */
    public int getNucletoideCode(String nucleotide)
    {
        int code = 0;
        while(!nucleotide.equals(this.nucletoides[code])) code++;
        return code;
    }
    
    /** ------------------------------------------------------------------------
     * Escreve em um arquivo de forma seqüencial uma matriz com os números reais 
     * que representam os nucletídeos após a codificaçao. 
     * @param desiredOutput 
     */
    public void recordFile(int desiredOutput)
    {
        for(int i = 0; i < 16; i++)
        {
            for(int j = 0; j < 4; j++ )
            {
                try
                {
                    writingFile.write(Float.toString(freqtrigram[i][j]) + '\t');
                }
                catch(IOException e) { e.printStackTrace(); }
            }
        }
        
        // valor gravado no final de cada linha eh a saida desejada
        try
        {
            writingFile.write(Integer.toString(desiredOutput));
            writingFile.write('\n');
        }
        catch(IOException e) { e.printStackTrace(); }
    }
    
    /** ------------------------------------------------------------------------
     * Lê um arquivo texto que contém as freqüências de trigrams e armazena em 
     * uma String que será posteriormente separada em tokens para o processamento 
     * da coficação.
     * @return variável que armazena todo o conteúdo do arquivo de freqüências.
     */
    public String readFile()
    {
        String str;
        String textFile = "";    
       
        try
        {
            while((str = readingFile.readLine()) != null) // acha o final do arquivo por meio do null
            {
                textFile += str + '\n';
            }
        }
        catch(IOException e) 
        { 
            e.printStackTrace(); 
        }
        return textFile;
    }
}

/** ============================================================================
 * A classe FiltroFrequencia extende a classe abstrata 
 * "javax.swing.filechooser.FileFilter" e filtra os tipos de arquivos 
 * aceitos, nesse caso arquivos *.frq.
 */
class FiltroFrequencia extends javax.swing.filechooser.FileFilter
{
    /**
     * Exibe apenas os arquivos definidos nesse filtro.
     * Método declarado  na classe abstrata FileFilter. 
     * @param file arquivo a ser filtrado. 
     */
    public boolean accept(File file) 
    {
        if(file.isDirectory()) return true;
  
        String fileName = file.getName();
        
        if(fileName.endsWith(".frq")) return true;
        
        return false;
    }

    /** ------------------------------------------------------------------------
     * Descrição do filtro.
     * Método declarado na classe abstrata FileFilter.
     */
    public String getDescription() 
    {
        return "Arquivos de freqüência (*.frq)";
    }
}

/** ============================================================================
 * A classe FiltroFrequencia extende a classe abstrata 
 * "javax.swing.filechooser.FileFilter" e filtra os tipos de arquivos 
 * aceitos, nesse caso arquivos *.frq.
 */
class FiltroSeq extends javax.swing.filechooser.FileFilter
{
    /** ------------------------------------------------------------------------
     * Exibe apenas os arquivos definidos nesse filtro.
     * Método declarado  na classe abstrata FileFilter. 
     * @param file arquivo a ser filtrado. 
     */
    public boolean accept(File file) 
    {
        if(file.isDirectory()) return true;
  
        String fileName = file.getName();
        
        if(fileName.endsWith(".seq")) return true;
        return false;
    }

    /** ------------------------------------------------------------------------
     * Descrição do filtro.
     * Método declarado na classe abstrata FileFilter.
     */
    public String getDescription() 
    {
        return "Arquivos de freqüência (*.frq)";
    }
}